/*
 * @(#)XfslThread.java        1.0 2000/05/09
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfsl.model;

import xfuzzy.lang.*;
import xfuzzy.xfds.XfdsDataSet;
import xfuzzy.xfds.XfdsParser;
import java.io.FileInputStream;
import java.util.Observable;

/**
 * Clase que desarrolla un proceso aprendizaje automatico por medio de
 * algoritmos de ajuste paramatrico. Puede funcionar como un hilo separado.
 * 
 * @author Francisco Jose Moreno Velo
 * 
 */
public class XfslThread extends Observable implements Runnable {

	// ----------------------------------------------------------------------------//
	// CONSTANTES P�BLICAS //
	// ----------------------------------------------------------------------------//

	/**
	 * CONSTANTES PARA SABER EL CAMBIO QUE HA HABIDO
	 */

	public static final int DEC_EPOCH = 1;
	public static final int FINAL_SPEC = 2;

	// ----------------------------------------------------------------------------//
	// MIEMBROS PRIVADOS //
	// ----------------------------------------------------------------------------//

	/**
	 * Configuracion del proceso
	 */
	private XfslConfig config;

	/**
	 * Estado del proceso
	 */
	private XfslStatus status;

	/**
	 * Tuberia de comunicacion con la ventana grafica
	 */
	private XfslPipe pipe;

	/**
	 * Sistema difuso sobre el que se aplica el proceso
	 */
	private Specification spec;

	/**
	 * Conjunto de datos de entrenamiento
	 */
	private XfdsDataSet training;

	/**
	 * Conjunto de datos de test
	 */
	private XfdsDataSet test;

	/**
	 * NOS DIR� EL �LTIMO CAMBIO EN EL OBJETO
	 */
	private int CHANGE;

	// ----------------------------------------------------------------------------//
	// CONSTRUCTOR //
	// ----------------------------------------------------------------------------//

	/**
	 * Constructor para ejecucion en modo comando
	 */
	public XfslThread(Specification spec, XfslConfig config) {
		this.spec = spec;
		this.config = config;
		this.status = new XfslStatus();
		this.pipe = null;
	}

	/**
	 * Constructor para ejecucion en modo grafico
	 */
	public XfslThread(Specification spec, XfslConfig config, XfslPipe pipe) {
		this.spec = spec;
		this.config = config;
		this.status = new XfslStatus();
		this.pipe = pipe;
	}

	// ----------------------------------------------------------------------------//
	// METODOS PUBLICOS //
	// ----------------------------------------------------------------------------//

	/**
	 * Ejecucion del hilo
	 */
	public void run() {
		try {
			learning();
		} catch (XflException ex) {
			if (this.pipe != null)
				this.pipe.putMessage(ex.toString());
			else
				System.out.println(ex.toString());
		}
	}

	/**
	 * Ejecucion completa del proceso de aprendizaje
	 */
	public void learning() throws XflException {

		initialization();
		status.status = XfslStatus.LEARNING;
		analize();
		communication();
		while (!config.endcondition.isOver(status)) {
			iteration();
			communication();
		}
		analize();
		status.status = XfslStatus.FINISHED;
		communication();
	}

	/**
	 * Ejecucion completa del proceso de aprendizaje
	 */
	public void tuning() throws XflException {
		initialization();
		status.status = XfslStatus.LEARNING;
		analize();

		while (!config.endcondition.isOver(status)) {
			iteration();
			this.CHANGE = XfslThread.DEC_EPOCH;
			setChanged();
			notifyObservers();
			clearChanged();
		}
		analize();
		status.status = XfslStatus.FINISHED;
		this.CHANGE = XfslThread.FINAL_SPEC;
		setChanged();
		notifyObservers();
		clearChanged();
	}

	/**
	 * Obtiene el estado de ejecucion
	 * 
	 * @return
	 */
	public XfslStatus getStatus() {
		return this.status;
	}

	// ----------------------------------------------------------------------------//
	// METODOS PRIVADOS //
	// ----------------------------------------------------------------------------//

	/**
	 * Inicializa el proceso de aprendizaje
	 */
	private void initialization() throws XflException {
		this.config.modified = false;
		this.config.setParameterSettings(this.spec.getTypes());
		this.spec.setAdjustable();
		if (this.config.testfile == null)
			this.status.testing = false;
		else
			this.status.testing = true;
		int numoutputs = spec.getSystemModule().getOutputs().length;

		try {
			XfdsParser trnparser = new XfdsParser(new FileInputStream(this.config.trainingfile));
			this.training = trnparser.parse();
		} catch (Exception ex) {}

		if (this.config.testfile == null)
			this.test = null;
		else {
			try {
				XfdsParser tstparser = new XfdsParser(new FileInputStream(this.config.testfile));
				this.test = tstparser.parse();
			} catch (Exception ex) {}
		}
		this.config.errorfunction.normalizeWeights(numoutputs);
	}

	/**
	 * Iteracion del proceso de ajuste
	 */
	private void iteration() {
		status.epoch++;
		try {
			status.trn = config.algorithm.iteration(spec, training, config.errorfunction);
			if (status.testing)
				status.tst = config.errorfunction.evaluate(spec, test, status.tst.error);
		} catch (XflException ex) {
			status.epoch--;
			status.status = XfslStatus.FINISHED;
			if (this.pipe != null)
				this.pipe.putMessage(ex.toString());
			else
				System.out.println(ex.toString());
		}
	}

	/**
	 * Evaluaci�n del sistema
	 */
	private void analize() {
		status.trn = config.errorfunction.evaluate(spec, training, status.trn.error);
		if (!status.testing)
			return;
		status.tst = config.errorfunction.evaluate(spec, test, status.tst.error);
	}

	/**
	 * Comunicaci�n del estado del sistema
	 */
	private void communication() throws XflException {
		this.config.logfile.write(this.status);
		if (this.pipe == null)
			return;
		this.pipe.put((XfslStatus) this.status.clone());
		int msg = this.pipe.getCommand();
		if (msg == XfslPipe.NOMSG)
			return;
		if (msg == XfslPipe.STOP) {
			int laststat = this.status.status;
			this.status.status = XfslStatus.STOPPED;
			this.pipe.put((XfslStatus) this.status.clone());
			while (msg != XfslPipe.CONTINUE && msg != XfslPipe.FINISH) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {}
				msg = this.pipe.getCommand();
			}
			if (msg == XfslPipe.FINISH)
				this.status.status = XfslStatus.FINISHED;
			if (msg == XfslPipe.CONTINUE) {
				this.status.status = laststat;
				if (this.config.modified)
					initialization();
			}
			this.pipe.put((XfslStatus) this.status.clone());
		}
	}

	/**
	 * Obtiene el valor del cambio notificado a los observadores
	 * 
	 * @return
	 */
	public int getChange() {
		return this.CHANGE;
	}

}
